// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// http://github.com/jskeet/dotnet-protobufs/
// Original C++/Java/Python code:
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Collections;
using System.Reflection;

namespace Google.ProtocolBuffers.FieldAccess {
  /// <summary>
  /// Accessor for a repeated field of type int, ByteString etc.
  /// </summary>
  internal class RepeatedPrimitiveAccessor<TMessage, TBuilder> : IFieldAccessor<TMessage, TBuilder>
      where TMessage : IMessage<TMessage, TBuilder>
      where TBuilder : IBuilder<TMessage, TBuilder> {

    private readonly Type clrType;
    private readonly Func<TMessage, object> getValueDelegate;
    private readonly Func<TBuilder, IBuilder> clearDelegate;
    private readonly Action<TBuilder, object> addValueDelegate;
    private readonly Func<TBuilder, object> getRepeatedWrapperDelegate;
    private readonly Func<TMessage, int> countDelegate;
    private readonly MethodInfo getElementMethod;
    private readonly MethodInfo setElementMethod;

    // Replacement for Type.EmptyTypes which apparently isn't available on the compact framework
    internal static readonly Type[] EmptyTypes = new Type[0];

    /// <summary>
    /// The CLR type of the field (int, the enum type, ByteString, the message etc).
    /// This is taken from the return type of the method used to retrieve a single
    /// value.
    /// </summary>
    protected Type ClrType {
      get { return clrType; }
    }

    internal RepeatedPrimitiveAccessor(string name) {      
      PropertyInfo messageProperty = typeof(TMessage).GetProperty(name + "List");
      PropertyInfo builderProperty = typeof(TBuilder).GetProperty(name + "List");
      PropertyInfo countProperty = typeof(TMessage).GetProperty(name + "Count");
      MethodInfo clearMethod = typeof(TBuilder).GetMethod("Clear" + name, EmptyTypes);
      getElementMethod = typeof(TMessage).GetMethod("Get" + name, new Type[] { typeof(int) });
      clrType = getElementMethod.ReturnType;
      MethodInfo addMethod = typeof(TBuilder).GetMethod("Add" + name, new Type[] { ClrType });
      setElementMethod = typeof(TBuilder).GetMethod("Set" + name, new Type[] { typeof(int), ClrType });
      if (messageProperty == null 
          || builderProperty == null 
          || countProperty == null
          || clearMethod == null
          || addMethod == null
          || getElementMethod == null
          || setElementMethod == null) {
        throw new ArgumentException("Not all required properties/methods available");
      }
      clearDelegate = (Func<TBuilder, IBuilder>)Delegate.CreateDelegate(typeof(Func<TBuilder, IBuilder>), null, clearMethod);
      countDelegate = (Func<TMessage, int>)Delegate.CreateDelegate
          (typeof(Func<TMessage, int>), null, countProperty.GetGetMethod());
      getValueDelegate = ReflectionUtil.CreateUpcastDelegate<TMessage>(messageProperty.GetGetMethod());
      addValueDelegate = ReflectionUtil.CreateDowncastDelegateIgnoringReturn<TBuilder>(addMethod);
      getRepeatedWrapperDelegate = ReflectionUtil.CreateUpcastDelegate<TBuilder>(builderProperty.GetGetMethod());
    }

    public bool Has(TMessage message) {
      throw new InvalidOperationException();
    }
    
    public virtual IBuilder CreateBuilder() {
      throw new InvalidOperationException();
    }

    public virtual object GetValue(TMessage message) {
      return getValueDelegate(message);
    }

    public void SetValue(TBuilder builder, object value) {
      // Add all the elements individually.  This serves two purposes:
      // 1) Verifies that each element has the correct type.
      // 2) Insures that the caller cannot modify the list later on and
      //    have the modifications be reflected in the message.
      Clear(builder);
      foreach (object element in (IEnumerable) value) {
        AddRepeated(builder, element);
      }
    }

    public void Clear(TBuilder builder) {
      clearDelegate(builder);
    }

    public int GetRepeatedCount(TMessage message) {
      return countDelegate(message);
    }

    public virtual object GetRepeatedValue(TMessage message, int index) {
      return getElementMethod.Invoke(message, new object[] {index } );
    }

    public virtual void SetRepeated(TBuilder builder, int index, object value) {
      ThrowHelper.ThrowIfNull(value, "value");
      setElementMethod.Invoke(builder, new object[] { index, value });
    }

    public virtual void AddRepeated(TBuilder builder, object value) {
      ThrowHelper.ThrowIfNull(value, "value");
      addValueDelegate(builder, value);
    }

    /// <summary>
    /// The builder class's accessor already builds a read-only wrapper for
    /// us, which is exactly what we want.
    /// </summary>
    public object GetRepeatedWrapper(TBuilder builder) {
      return getRepeatedWrapperDelegate(builder);
    }
  }
}